﻿using System;
using System.Collections.Generic;
using System.Text;
using TipTepOpenNI;
using TipTepCore;
using TipTep.ImageAnalyzer;
using System.Drawing;

/// <summary>
/// Provides classes and methods for depth image analyzation and hand skeleton recognition
/// </summary>
namespace TipTepHandSkeletonnet
{
    /// <summary>
    /// The main class used for hand skeleton recognition
    /// </summary>
    public class HandSkeleton
    {
        private SensorConnector sensorConnector;
        /// <summary>
        /// Represents the method, which will handle a Frame/Skeleton update event
        /// </summary>
        /// <param name="sender">System parameter</param>
        /// <param name="rgb">RGB image</param>
        /// <param name="depth">Depth image</param>
        /// <param name="handPoints">Recognised hand points</param>
        /// <param name="handSkeleton">Recognised hand skeleton</param>
        /// <param name="handRegion">Hand surrounding rectangle</param>
        public delegate void DelegateSkeletonUpdate(object sender, Frame rgb, Frame depth, Point3D[] handPoints, Skeleton3D handSkeleton, RectangleF handRegion);
        /// <summary>
        /// Event is raised every time new frame is arrived from the sensor
        /// </summary>
        public event DelegateSkeletonUpdate SkeletonUpdate;
        private bool generate = true;
        int pruningParameter = 20;
        int areaIgnoreParameter = 100;

        /// <summary>
        /// Constructor for <code>HandSkeleton</code> class
        /// </summary>
        /// <param name="pruningParameter">Skeleton pruning parameter</param>
        /// <param name="areaIgnoreParameter">Hand area ignore parameter</param>
        public HandSkeleton(int pruningParameter, int areaIgnoreParameter)
        {
            this.pruningParameter = (pruningParameter < 5 ? 5 : pruningParameter);
            this.areaIgnoreParameter = (areaIgnoreParameter < 10 ? 10 : areaIgnoreParameter);
            sensorConnector = new SensorConnector();
            sensorConnector.UpdateImage += new SensorConnector.ImageHandler(sensorInit_UpdateImage);
        }

        /// <summary>
        /// This event is called every time, when the new image comes from the sensor.
        /// This function must work in LESS THAN 40 MILLISECONDS, otherwise hand point will not be recognised by NITE.
        /// </summary>
        /// <param name="sender">Sensor connector</param>
        /// <param name="rgb">RGB image</param>
        /// <param name="depth">Depth image</param>
        /// <param name="handPoints">The array of recognised hand points</param>
        unsafe void sensorInit_UpdateImage(object sender, Frame rgb, Frame depth, Point3D[] handPoints)
        {
            if (!generate)
            {
                (sender as SensorConnector).Stop();
            }
            int maxDepth = 10000;
            TipTepSkeletonization.Skeleton pSkeleton = null;
            Skeleton3D skeleton3D = new Skeleton3D();
            RectangleF handRectangle = RectangleF.Empty;
            if (SkeletonUpdate != null && handPoints.Length > 0 && handPoints[0] != null && handPoints[0].Z > 0)
            {
                ValueType[] depthMap = depth.GetData();
                DepthImage depthImage = new DepthImage(depth.GetWidth(), depth.GetHeight(), (ushort)maxDepth, depthMap);
                HandImageExtractor handImageExtractor = new HandImageExtractor(depthImage, handPoints[0], (ushort)maxDepth);
                DepthImage handImage = handImageExtractor.GetExtractedHandImage();

                #region Via Mestetskiy

                if (handImage != null)
                {

                    Bitmap bmp = handImage.Get2DImage(ImageTransferMode2D.Monochrome);
                    byte[] img = handImage.Get2DImage();
                    fixed (byte* pImg = img)
                    {
                        TipTepSkeletonization.Skeletonization skeletonization = new TipTepSkeletonization.Skeletonization();
                        pSkeleton = skeletonization.RetrieveSkeleton(pImg, handImage.Height, handImage.Width, pruningParameter, areaIgnoreParameter);
                        if (pSkeleton != null)
                        {
                            skeleton3D.Lines = new List<Line3D>();
                            int k = 0;
                            float x = handImageExtractor.GetHandImageSize().X;
                            float y = handImageExtractor.GetHandImageSize().Y + handImageExtractor.GetHandImageSize().Height;
                            while (pSkeleton.p1X[k] + pSkeleton.p1Y[k] + pSkeleton.p2X[k] + pSkeleton.p2Y[k] > 0)
                            {
                                skeleton3D.Lines.Add(new Line3D(
                                    x + pSkeleton.p1X[k],
                                    y - pSkeleton.p1Y[k],
                                    (int)handImage.GetDistanceAt(pSkeleton.p1X[k], pSkeleton.p1Y[k]),
                                    x + pSkeleton.p2X[k],
                                    y - pSkeleton.p2Y[k],
                                    (int)handImage.GetDistanceAt(pSkeleton.p2X[k], pSkeleton.p2Y[k])
                                    ));
                                k++;
                            }
                        }
                    }
                }
                handRectangle = handImageExtractor.GetHandImageSize();

                #endregion
            }
            SkeletonUpdate(this, rgb, depth, handPoints, skeleton3D, handRectangle);
        }

        /// <summary>
        /// Start depth video streaming
        /// </summary>
        public void Start()
        {
            if (sensorConnector != null)
            {
                sensorConnector.Start();
            }
        }

        /// <summary>
        /// Stop streaming
        /// </summary>
        public void Stop()
        {
            generate = false;
            sensorConnector.Stop();
        }

        /// <summary>
        /// Determines, if streaming is stoped
        /// </summary>
        /// <returns></returns>
        public bool IsStoped()
        {
            return !generate;
        }             
    }
}
